<template>
    <div class="ojSass ai-chat-dialog-container">
        <div class="ai-chat-floating-btn cursor-pointer" ref="floatingButtonRef" @mousedown="handleMouseDown"
            :style="buttonStyle" title="AI 助手" v-show="!dialogVisible" @contextmenu.prevent="showContextMenu"
            @click="openDialog">
            <img :src="currentFloatingButtonGif" alt="AI Assistant" class="floating-btn-gif" />
            <div v-show="contextMenuVisible" ref="contextMenuRef" class="custom-context-menu"
                :style="{ top: contextMenuPosition.top, left: contextMenuPosition.left }">
                <div class="context-menu-item" @click.stop="openIframeInNewTab">新页面打开</div>
            </div>
        </div>

        <!-- :show-close="chatMode !== 'voice'" -->
        <el-dialog v-if="dialogVisible" v-model="dialogVisible" :width="DIALOG_WIDTH" append-to=".ojSass"
            :z-index="DIALOG_Z_INDEX" :modal-append-to-body="false" draggable overflow :modal="false"
            :class="{ 'voice-mode-dialog': chatMode === 'voice' }">
            <template #header v-if="chatMode !== 'voice'">
                <span class="icon-text el-dialog__header-title">
                    <el-icon class="text-primary">
                        <Guide />
                    </el-icon>
                    AI 助手
                    <AIWarningTips :show="true" class="margin-left20" />
                </span>
            </template>

            <!-- Text Chat View -->
            <div v-if="chatMode === 'text'">
                <div class="dialog-main-content">
                    <div class="ai-chat-dialog-header">
                        <div class="dialog-clear-btn">
                            <el-button @click="clearChatHistory"
                                :disabled="messages.length === 0 && !isAiResponding">清空记录（开启新会话）</el-button>
                        </div>
                    </div>
                    <div class="chat-messages-container" ref="messagesContainerRef">
                        <div v-if="messages.length === 0 && !isAiResponding" class="empty-chat-placeholder">
                            你好，你可以提问这个页面的相关内容 😊
                        </div>
                        <div v-for="msg in displayMessages" :key="msg.id" class="message-row"
                            :class="`message-${msg.role}`">
                            <div class="message-avatar">
                                <span v-if="msg.role === MessageRole.User" class="emoji">{{ USER_AVATAR_EMOJI }}</span>
                                <span v-if="msg.role === MessageRole.Assistant" class="emoji">{{ ASSISTANT_AVATAR_EMOJI
                                }}</span>
                            </div>
                            <div class="message-content-wrapper">
                                <div class="message-sender-name">{{ msg.role === MessageRole.User ? (props.user?.name ||
                                    '我') :
                                    'AI助手' }}
                                </div>
                                <div class="message-bubble">
                                    <div v-if="msg.role === MessageRole.Assistant"
                                        v-html="formatAIResponse(msg.content)" />
                                    <div v-else>
                                        <template v-if="parseMessageContentForAudio(msg.content).audioUrl">
                                            <div class="message-text-content">{{
                                                parseMessageContentForAudio(msg.content).text }}</div>
                                            <audio controls :src="parseMessageContentForAudio(msg.content).audioUrl!"
                                                class="chat-audio-player"></audio>
                                        </template>
                                        <template v-else>
                                            {{ msg.content }}
                                        </template>
                                    </div>
                                </div>
                            </div>
                        </div>
                        <div v-if="shouldShowThinkingIndicator" class="message-row message-assistant">
                            <div class="message-avatar"><span class="emoji">{{ ASSISTANT_AVATAR_EMOJI }}</span></div>
                            <div class="message-content-wrapper">
                                <div class="message-sender-name">AI助手</div>
                                <div class="message-bubble">
                                    <el-icon class="is-loading">
                                        <Loading />
                                    </el-icon> 正在思考中...
                                </div>
                            </div>
                        </div>
                    </div>
                    <div class="chat-input-area">
                        <el-button v-if="showScrollToBottomButton" class="scroll-to-bottom-btn" :icon="ArrowDownBold"
                            circle size="small" @click="handleScrollToBottomClick" title="滚动到底部" />
                        <div class="chat-input-inner">
                            <el-input ref="inputRef" v-model="input" placeholder="输入您的问题... (Shift+Enter 换行, ↑/↓ 历史)"
                                @keydown.enter.exact.prevent="handleSendMessage"
                                @keydown.arrow-up.prevent="handleArrowUp" @keydown.arrow-down.prevent="handleArrowDown"
                                clearable type="textarea" :autosize="{ minRows: 1, maxRows: TEXTAREA_MAX_ROWS }"
                                resize="none" class="chat-textarea" :disabled="isDictating" />
                        </div>
                        <div class="chat-controls-row">
                            <div v-if="isDictating" class="dictation-choices">
                                <el-button @click="handleDiscardDictation" :icon="CloseIcon" circle
                                    title="丢弃录音"></el-button>
                                <el-button type="primary" @click="handleUploadAndApplyDictation" :icon="CheckIcon"
                                    circle title="确认并使用录音"></el-button>
                            </div>
                            <template v-else>
                                <el-tooltip content="开始听写" placement="top"
                                    :disabled="!canUseSpeechRecognition || !canUseMediaRecorder">
                                    <el-button circle class="dictation-btn" :type="'default'" :icon="MicrophoneIcon"
                                        @click="toggleDictation"
                                        :disabled="!canUseSpeechRecognition || !canUseMediaRecorder || isAiResponding"
                                        :title="!canUseSpeechRecognition || !canUseMediaRecorder ? '浏览器不支持听写功能' : '开始听写'"
                                        aria-label="听写" />
                                </el-tooltip>
                                <el-button circle class="send-stop-button" :type="isAiResponding ? 'danger' : 'primary'"
                                    :icon="isAiResponding ? VideoPause : Promotion"
                                    @click="isAiResponding ? stopChat() : handleSendMessage()"
                                    :disabled="(!isAiResponding && !input.trim())" aria-label="发送或停止" />
                            </template>
                        </div>
                    </div>
                </div>
                <div class="mode-switch-footer text-mode-footer">
                    <el-button @click="toggleChatMode" size="">切换语音模式</el-button>
                </div>
            </div>

            <!-- Voice Chat View (Iframe) -->
            <div v-if="chatMode === 'voice'">
                <div class="dialog-main-content voice-mode-main-content">
                    <div class="voice-chat-header-text" v-if="chatMode !== 'voice'">语音聊天界面</div>
                    <iframe ref="voiceIframeRef" :src="computedIframeUrl" class="voice-chat-iframe-element"
                        allow="microphone"></iframe>
                </div>
                <!-- <div class="mode-switch-footer voice-mode-footer">
                    <el-button @click="toggleChatMode" size="">切换文字模式</el-button>
                </div> -->
            </div>

        </el-dialog>
    </div>
</template>

<script lang="ts" setup>
import { useChat } from '@ai-sdk/vue';
import { ArrowDownBold, Check as CheckIcon, Close as CloseIcon, Guide, Loading, Microphone as MicrophoneIcon, Promotion, VideoPause } from '@element-plus/icons-vue';
import { ElMessage } from 'element-plus';
import { encode } from 'js-base64'; // Base64 编码工具函数
import { marked } from 'marked';
import { v4 as uuidv4 } from 'uuid';
import { computed, CSSProperties, inject, nextTick, onUnmounted, ref, watch } from 'vue'; // Added CSSProperties
import { Store } from 'vuex'; // Keep Store type
const DefaultGif = 'https://cdn.hterobot.com/JOY/image/noTalk.gif'; // 默认状态 GIF
const SadGif = 'https://cdn.hterobot.com/JOY/image/sad.gif'; // 错误状态 GIF
const TalkingGif = 'https://cdn.hterobot.com/JOY/image/talking.gif'; // AI讲话状态 GIF
const ThinkingGif = 'https://cdn.hterobot.com/JOY/image/thinking.gif'; // AI思考状态 GIF
import { Storekey } from '../../interface'; // Vuex Store Key
import Studycodemodel from '../../mysql_interface/studycodemodel'; // 用户模型类型
import { AILogAction, AILogMethod } from '../../util/data'; // 管理员团队ID及日志相关枚举
import AIWarningTips from '../AIWarningTips.vue'; // AI警告提示组件

// --- 常量定义 ---
const DIALOG_WIDTH = '60%'; // 对话框宽度
const TEXTAREA_MAX_ROWS = 6; // 文本输入区域最大行数
const MAX_LOG_CONTENT_LENGTH = 2000; // 日志内容最大长度
const USER_AVATAR_EMOJI = '🧑‍💻'; // 用户头像 Emoji
const ASSISTANT_AVATAR_EMOJI = '🤖'; // AI助手头像 Emoji
const DIALOG_Z_INDEX = 2009; // 对话框及浮动按钮层级
const SCROLL_THRESHOLD = 100; // 触发"滚动到底部"按钮出现的滚动阈值（像素）
const DRAG_THRESHOLD = 5; // 拖拽阈值：移动超过此像素数才认为是拖拽（用于抑制点击）

/* --- Draggable Floating Button State --- */
const floatingButtonRef = ref<HTMLElement | null>(null);
const isDragging = ref(false);
const dragOffsetX = ref(0); // 鼠标相对于按钮左边缘的偏移
const dragOffsetY = ref(0); // 鼠标相对于按钮上边缘的偏移
const buttonStyle = ref<Partial<CSSProperties>>({ cursor: 'grab' }); // 按钮动态样式
const didDrag = ref(false); // 标记是否发生了拖拽（用于区分点击和拖拽）
const initialMouseX = ref(0); // 初始鼠标X位置（用于拖拽阈值判断）
const initialMouseY = ref(0); // 初始鼠标Y位置（用于拖拽阈值判断）

/**
 * 处理鼠标按下事件，开始拖拽操作。
 * @param {MouseEvent} event 鼠标按下事件
 */
const handleMouseDown = (event: MouseEvent) => {
    if (event.button !== 0) return; // 只允许左键拖拽

    event.preventDefault(); // 阻止默认行为
    event.stopPropagation(); // 阻止事件冒泡

    isDragging.value = true;
    didDrag.value = false; // 重置拖拽标记
    initialMouseX.value = event.clientX;
    initialMouseY.value = event.clientY;
    buttonStyle.value.cursor = 'grabbing';

    if (floatingButtonRef.value) {
        const rect = floatingButtonRef.value.getBoundingClientRect();
        dragOffsetX.value = event.clientX - rect.left;
        dragOffsetY.value = event.clientY - rect.top;
    }

    // 添加事件监听器到 document，确保即使鼠标移出按钮也能继续拖拽
    document.addEventListener('mousemove', handleMouseMove, { passive: false });
    document.addEventListener('mouseup', handleMouseUp, { passive: false });
    document.addEventListener('mouseleave', handleMouseUp, { passive: false });

    // 禁用文本选择
    document.body.style.userSelect = 'none';
    document.body.style.webkitUserSelect = 'none';
};

/**
 * 处理鼠标移动事件，更新按钮位置。
 * @param {MouseEvent} event 鼠标移动事件
 */
const handleMouseMove = (event: MouseEvent) => {
    if (!isDragging.value || !floatingButtonRef.value) return;

    event.preventDefault(); // 阻止默认行为

    // 检查是否超过拖拽阈值
    if (!didDrag.value) {
        const dx = Math.abs(event.clientX - initialMouseX.value);
        const dy = Math.abs(event.clientY - initialMouseY.value);
        if (dx > DRAG_THRESHOLD || dy > DRAG_THRESHOLD) {
            didDrag.value = true; // 标记为真正的拖拽
        }
    }

    const buttonWidth = floatingButtonRef.value.offsetWidth;
    const buttonHeight = floatingButtonRef.value.offsetHeight;

    let newLeft = event.clientX - dragOffsetX.value;
    let newTop = event.clientY - dragOffsetY.value;

    // 限制在视口内
    newLeft = Math.max(0, Math.min(newLeft, window.innerWidth - buttonWidth));
    newTop = Math.max(0, Math.min(newTop, window.innerHeight - buttonHeight));

    // 使用 right 和 bottom 定位
    buttonStyle.value.right = `${window.innerWidth - newLeft - buttonWidth}px`;
    buttonStyle.value.bottom = `${window.innerHeight - newTop - buttonHeight}px`;
    delete buttonStyle.value.left;
    delete buttonStyle.value.top;
};

/**
 * 处理鼠标释放事件，结束拖拽操作。
 */
const handleMouseUp = () => {
    if (!isDragging.value) return;

    isDragging.value = false;
    buttonStyle.value.cursor = 'grab';

    // 移除事件监听器
    document.removeEventListener('mousemove', handleMouseMove);
    document.removeEventListener('mouseup', handleMouseUp);
    document.removeEventListener('mouseleave', handleMouseUp);

    // 恢复文本选择
    document.body.style.userSelect = '';
    document.body.style.webkitUserSelect = '';
};


/* --- 右键菜单状态 --- */
const contextMenuVisible = ref(false);
const contextMenuPosition = ref({ top: '0px', left: '0px' });
const contextMenuRef = ref<HTMLElement | null>(null);

/**
 * 显示自定义右键菜单。
 * @param {MouseEvent} event 鼠标事件
 */
const showContextMenu = (event: MouseEvent) => {
    event.preventDefault();
    contextMenuVisible.value = true;

    nextTick(() => {
        if (!contextMenuRef.value || !event.currentTarget) return;

        const menu = contextMenuRef.value;
        const button = event.currentTarget as HTMLElement; // The floating button
        const buttonRect = button.getBoundingClientRect(); // Get button's position in viewport

        const menuWidth = menu.offsetWidth;
        const menuHeight = menu.offsetHeight;
        const viewportWidth = window.innerWidth;
        const viewportHeight = window.innerHeight;

        // 步骤 1: 计算菜单在视口中的理想初始 top-left 坐标 (基于鼠标点击位置)
        let targetViewportX = event.clientX;
        let targetViewportY = event.clientY;

        // 步骤 2: 调整理想视口坐标，防止菜单超出视口边界
        // 如果菜单会超出视口右边界，则将菜单定位到鼠标左侧
        if (targetViewportX + menuWidth > viewportWidth) {
            targetViewportX = event.clientX - menuWidth;
        }
        // 如果菜单会超出视口下边界，则将菜单定位到鼠标上侧
        if (targetViewportY + menuHeight > viewportHeight) {
            targetViewportY = event.clientY - menuHeight;
        }

        // 确保菜单不会因为调整位置后反而跑到视口左侧或上侧外面
        if (targetViewportX < 0) {
            targetViewportX = 0; // 贴近左边缘
        }
        if (targetViewportY < 0) {
            targetViewportY = 0; // 贴近上边缘
        }

        // 再次检查，如果菜单本身比视口还宽或高（极端情况），确保它至少贴边显示而不是完全消失
        // 这一步确保即使调整后，如果菜单太大，它会被限制在视口内
        if (targetViewportX + menuWidth > viewportWidth) {
            targetViewportX = viewportWidth - menuWidth;
        }
        if (targetViewportY + menuHeight > viewportHeight) {
            targetViewportY = viewportHeight - menuHeight;
        }

        // 步骤 3: 将最终的理想视口坐标转换为相对于按钮的坐标
        // 因为 .custom-context-menu 的 position 是 absolute，其 top/left 是相对于 .ai-chat-floating-btn 的
        const relativeTop = targetViewportY - buttonRect.top;
        const relativeLeft = targetViewportX - buttonRect.left;

        contextMenuPosition.value = {
            top: `${relativeTop}px`,
            left: `${relativeLeft}px`,
        };

        document.addEventListener('mousedown', handleClickOutside);
    });
};

/**
 * 隐藏自定义右键菜单。
 */
const hideContextMenu = () => {
    contextMenuVisible.value = false;
    document.removeEventListener('mousedown', handleClickOutside);
};

/**
 * 处理页面点击事件，如果点击发生在菜单外部，则关闭菜单。
 * @param {MouseEvent} event 鼠标事件
 */
const handleClickOutside = (event: MouseEvent) => {
    if (contextMenuRef.value && !contextMenuRef.value.contains(event.target as Node)) {
        hideContextMenu();
    }
};

/**
 * 在新标签页中打开 Iframe URL。
 */
const openIframeInNewTab = () => {
    const url = newTabUrl.value; // 使用新的 newTabUrl 计算属性
    if (url) {
        window.open(url, '_blank');
    }
    hideContextMenu();
};

/* --- 聊天模式状态 --- */
const chatMode = ref<'text' | 'voice'>('voice'); // 'text' 或 'voice'

/* --- 枚举定义 --- */
enum MessageRole { // 消息角色枚举
    User = 'user',
    Assistant = 'assistant',
}

enum IframeMessageType { // iframe 消息类型枚举
    Height = 'iframe-height',
    Close = 'iframe-close',
}

/* --- 组件 Props 定义 --- */
const props = defineProps<{
    user: Studycodemodel.Iuser, // 当前用户信息
    contextDataString?: string, // 外部传入的上下文数据字符串
    enableInNoStoreContext?: boolean, // 新增 prop，允许在无 store 环境下基于用户存在性显示
}>();

/* --- Vuex Store 与依赖注入 --- */
// const store = useStore(Storekey); // Vuex Store 实例 - Old way
const store = inject<Store<any> | undefined>(Storekey, undefined); // New way: Inject store, default to undefined if not found
const tenant_id = inject<string | undefined>("tenant_id", undefined);
/* --- 对话框状态与 DOM 引用 --- */
const dialogVisible = ref(false); // 对话框可见性状态
const messagesContainerRef = ref<HTMLElement | null>(null); // 聊天消息容器的 DOM 引用
const inputRef = ref<any>(null); // 输入框的 DOM 引用
const voiceIframeRef = ref<HTMLIFrameElement | null>(null); // 语音模式 iframe 引用
const showScrollToBottomButton = ref(false); // 是否显示"滚动到底部"按钮

/* --- 环境与 API 配置 --- */
const teamData = computed(() => { // 计算属性：获取当前团队列表
    // 安全地访问 store 和 team 数据
    if (store && store.state && store.state.team && store.state.team.rows) {
        return store.state.team.rows;
    }
    return []; // 如果 store 或 team 数据不可用，返回空数组
});

/**
 * 从 document.cookie 中获取指定名称的 cookie 值。
 * @param {string} name cookie 名称
 * @returns {string | null} cookie 值或 null
 */
function getCookie(name: string): string | null {
    const value = `; ${document.cookie}`;
    const parts = value.split(`; ${name}=`);
    if (parts.length === 2) {
        const part = parts.pop();
        if (part) return part.split(';').shift() || null; // 添加 || null 以确保类型匹配
    }
    return null;
}

const headers = computed(() => ({ // HTTP 请求头，包含 CSRF token
    'x-csrf-token': getCookie('csrfToken') || '',
    'Content-Type': 'application/json',
}));

const currentHostname = window.location.hostname; // 当前主机名
const isPublicHterobot = currentHostname === 'public.hterobot.com'; // 是否为 public.hterobot.com 环境

/**
 * 根据当前环境获取 API 基础 URL。
 * @returns {string} API 基础 URL
 */
const getApiBaseUrl = (): string => {
    if (process.env.NODE_ENV === 'development') {
        return ''; // 开发环境通常使用相对路径或代理
    }
    if (isPublicHterobot) {
        return ``; // public.hterobot.com 环境的特定基础 URL (如果需要)
    }
    if(tenant_id === '马克行') {
        return 'https://www.accoders.cn';
    }
    return  'https://oj.hterobot.com';
};
const API_BASE_URL = getApiBaseUrl(); // API 基础 URL
const API_ENDPOINT = '/api/studycode/v1/user/chatv1/completions'; // AI 聊天 API 端点

/* --- AI Chat App URL 配置 --- */
const AI_CHAT_APP_BASE_URL_DEV = 'http://localhost:5173';
const AI_CHAT_APP_BASE_URL_PROD = 'https://cdn.hterobot.com/AUIAICall-WEB/dist';

const AI_CHAT_IFRAME_PATH_DEV = '/ifarme';
const AI_CHAT_IFRAME_PATH_PROD = '/ifarme.html';

const AI_CHAT_NEW_TAB_PATH_DEV = ''; // 指向开发服务器根目录
const AI_CHAT_NEW_TAB_PATH_PROD = '/index.html';

const agentType = 0; // agentType 已在原代码中定义

/**
 * 构建 AI 聊天应用的 URL (包括 iframe 和新标签页)
 * @param base 基础 URL (环境相关)
 * @param path 页面路径 (例如 /ifarme.html, /index.html, 或空字符串)
 * @param contextData 上下文数据
 * @param userId 用户ID
 * @param userName 用户名
 * @returns {string} 完整的 URL
 */
function constructAIChatUrl(base: string, path: string, contextData?: string, userId?: number, userName?: string): string {
    const userDataPayload = {
        user: { '用户ID': userId, '用户名': userName },
        data: contextData,
    };
    const encodedUserData = encode(JSON.stringify(userDataPayload), true); // urlSafe = true
    return `${base}${path}?agentType=${agentType}&userData=${encodedUserData}`;
}

const computedIframeUrl = computed(() => {
    if (process.env.NODE_ENV === 'development') {
        return constructAIChatUrl(AI_CHAT_APP_BASE_URL_DEV, AI_CHAT_IFRAME_PATH_DEV, props.contextDataString, props.user?.ID, props.user?.name);
    }
    return constructAIChatUrl(AI_CHAT_APP_BASE_URL_PROD, AI_CHAT_IFRAME_PATH_PROD, props.contextDataString, props.user?.ID, props.user?.name);
});

const newTabUrl = computed(() => {
    if (process.env.NODE_ENV === 'development') {
        return constructAIChatUrl(AI_CHAT_APP_BASE_URL_DEV, AI_CHAT_NEW_TAB_PATH_DEV, props.contextDataString, props.user?.ID, props.user?.name);
    }
    return constructAIChatUrl(AI_CHAT_APP_BASE_URL_PROD, AI_CHAT_NEW_TAB_PATH_PROD, props.contextDataString, props.user?.ID, props.user?.name);
});


/* --- Iframe URL 计算 --- */
// const computedIframeUrl = computed(() => getAIChatIframeUrl()); // 旧的计算属性，将被上面的新定义覆盖

/**
 * 切换聊天模式（文字/语音）。
 */
const toggleChatMode = () => {
    if (chatMode.value === 'text') {
        chatMode.value = 'voice';
        // 从文本切换到语音时，停止当前AI响应和听写
        if (isAiResponding.value) stop();
        if (isDictating.value) resetDictationStateCompletely();
    } else {
        chatMode.value = 'text';
        // 从语音切换回文本时，聚焦输入框并滚动到底部
        nextTick(() => {
            inputRef.value?.focus();
            scrollToBottom();
        });
    }
};

/* --- 会话与 UUID 管理 --- */
const chatSessionId = ref<string>(''); // 当前聊天会话 ID
const chatSessionIdStorageKey = computed(() => `chat_session_id_${props.user?.ID}`); //会话 ID 在 localStorage 中的键名

/**
 * 管理聊天会话 ID。
 * 如果 reset 为 true 或本地存储中没有，则生成新的会话 ID 并存储。
 * @param {boolean} reset 是否强制重置会话 ID
 */
function manageChatSessionId(reset: boolean = false): void {
    if (!props.user?.ID) { // 用户 ID 不存在时，直接生成临时 UUID
        chatSessionId.value = uuidv4();
        return;
    }
    if (reset) {
        chatSessionId.value = uuidv4();
        localStorage.setItem(chatSessionIdStorageKey.value, chatSessionId.value);
    } else {
        const storedId = localStorage.getItem(chatSessionIdStorageKey.value);
        if (storedId) {
            chatSessionId.value = storedId;
        } else {
            chatSessionId.value = uuidv4();
            localStorage.setItem(chatSessionIdStorageKey.value, chatSessionId.value);
        }
    }
}

const currentUserMessageUuid = ref<string>(''); // 当前用户待发送消息的 UUID
const currentUserMessageUuidStorageKey = computed(() => `current_user_message_uuid_${props.user?.ID}`); // 用户消息 UUID 在 localStorage 中的键名

/**
 * 管理当前用户消息的 UUID。
 * 用于在发送前唯一标识用户的单次输入行为。
 * 如果 reset 为 true 或本地存储中没有，则生成新的 UUID 并存储。
 * @param {boolean} reset 是否强制重置消息 UUID
 */
function manageCurrentUserMessageUuid(reset: boolean = false): void {
    if (!props.user?.ID) { // 用户 ID 不存在时，直接生成临时 UUID
        currentUserMessageUuid.value = uuidv4();
        return;
    }
    if (reset) {
        currentUserMessageUuid.value = uuidv4();
        localStorage.setItem(currentUserMessageUuidStorageKey.value, currentUserMessageUuid.value);
    } else {
        const storedUuid = localStorage.getItem(currentUserMessageUuidStorageKey.value);
        if (storedUuid) {
            currentUserMessageUuid.value = storedUuid;
        } else {
            currentUserMessageUuid.value = uuidv4();
            localStorage.setItem(currentUserMessageUuidStorageKey.value, currentUserMessageUuid.value);
        }
    }
}

/* --- AI SDK (useChat) 核心 --- */
const {
    messages,     // 聊天消息数组 (类型: Ref<Message[]>)
    input,        // 当前输入框的内容 (类型: Ref<string>)
    handleSubmit, // 处理消息发送的函数
    isLoading,    // 指示 AI 是否正在生成回复 (类型: Ref<boolean>)
    stop,         // 停止 AI 生成的函数
    setMessages,  // 设置消息数组的函数
} = useChat({
    api: `${API_BASE_URL}${API_ENDPOINT}`, // AI 聊天 API 完整地址
    headers: headers.value,               // 请求头
    initialMessages: [],                  // 初始消息数组
    body: {},                             // 默认请求体，可在 handleSubmit 时覆盖
    onResponse: () => {                   // AI 开始响应时的回调
        isOnError.value = false;          // 重置错误状态
        isStreaming.value = true;         // 标记开始流式接收
    },
    onFinish: (message) => {              // AI 回复完成时的回调
        isStreaming.value = false;        // 标记流式接收结束
        scrollToBottom();                 // 滚动到底部
        if (message.role === MessageRole.Assistant) { // 如果是 AI 的回复
            recordAiLog(message.content, MessageRole.Assistant, message.id); // 记录日志
        }
    },
    onError: (error) => {                 // 发生错误时的回调
        isOnError.value = true;           // 设置错误状态
        isStreaming.value = false;        // 标记流式接收结束（因错误）
        if (error.name === 'AbortError') { // 如果是用户主动中止
            scrollToBottom();             // 确保滚动到底部
            return; // ElMessage.info 已在 stopChat 中处理
        }
        ElMessage.error(`AI 聊天请求错误: ${error.message}`); // 显示全局错误提示

        const errorDisplayText = `😥 抱歉，处理您的请求时遇到了问题。您可以尝试重新发送或稍后再试。`;
        const currentMessages = messages.value;
        let newMessagesArray = [...currentMessages];

        if (newMessagesArray.length > 0) {
            const lastMessageIndex = newMessagesArray.length - 1;
            const lastMessage = newMessagesArray[lastMessageIndex];
            if (lastMessage.role === MessageRole.Assistant) { // 更新最后一条 AI 消消息为错误提示
                newMessagesArray[lastMessageIndex] = {
                    ...lastMessage,
                    content: errorDisplayText,
                    parts: [{ type: 'text', text: errorDisplayText }],
                };
            } else { // 追加新的 AI 错误消息
                newMessagesArray.push({
                    id: uuidv4(), role: MessageRole.Assistant, content: errorDisplayText,
                    parts: [{ type: 'text', text: errorDisplayText }], createdAt: new Date()
                });
            }
        } else { // 如果消息列表为空，直接添加错误消息
            newMessagesArray.push({
                id: uuidv4(), role: MessageRole.Assistant, content: errorDisplayText,
                parts: [{ type: 'text', text: errorDisplayText }], createdAt: new Date()
            });
        }
        setMessages(newMessagesArray); // 更新消息列表以显示错误
        scrollToBottom();
    },
});

/* --- AI 响应状态 --- */
const isStreaming = ref(false); // 自定义流式响应状态，补充 isLoading，用于更精确控制 UI (例如 GIF)
const isAiResponding = computed(() => isLoading.value); // AI 是否正在响应 (综合 isLoading)
const isOnError = ref(false); // 是否发生了 API 错误

/* --- 听写功能状态与逻辑 --- */
const isDictating = ref(false); // 是否正在进行听写（录音和识别中），此状态控制UI显隐
const mediaRecorderInstance = ref<MediaRecorder | null>(null); // MediaRecorder 实例
const audioChunks = ref<Blob[]>([]); // 存储录音的 Blob 片段
const recognitionInstance = ref<any | null>(null); // SpeechRecognition 实例
const SpeechRecognitionAPI = (window as any).SpeechRecognition || (window as any).webkitSpeechRecognition; // 浏览器 SpeechRecognition API
const canUseSpeechRecognition = !!SpeechRecognitionAPI; // 是否支持 SpeechRecognition API
const canUseMediaRecorder = !!(navigator.mediaDevices && navigator.mediaDevices.getUserMedia); // 是否支持 MediaRecorder API
let dictationBaseText = ""; // 听写开始前输入框已有的文本，用于拼接后续识别内容
const recordedAudioBlob = ref<Blob | null>(null); // 最终录制的完整音频 Blob
const isAwaitingDictationChoice = ref(false); // 是否已录制完成并等待用户选择（丢弃/使用）
let resolveMediaRecorderStoppedPromise: (() => void) | null = null; // 用于等待 MediaRecorder.stop() 完成的 Promise resolve 函数
const audioUrlForNextMessage = ref<string | null>(null); // 存储待发送消息的音频 URL (上传到 OSS 后)

/**
 * 完全重置听写相关的状态和实例。
 */
const resetDictationStateCompletely = () => {
    // 停止并清理 SpeechRecognition 实例
    if (recognitionInstance.value) {
        try { recognitionInstance.value.abort(); } catch (e) { console.warn("重置时中止语音识别出错:", e); }
        recognitionInstance.value.onresult = null;
        recognitionInstance.value.onerror = null;
        recognitionInstance.value.onend = null;
        recognitionInstance.value = null;
    }
    // 停止并清理 MediaRecorder 实例
    if (mediaRecorderInstance.value) {
        const recorder = mediaRecorderInstance.value;
        if (recorder.state === "recording") {
            try { recorder.stop(); } catch (e) { console.warn("重置时停止录音出错:", e); }
        }
        if (recorder.stream) {
            recorder.stream.getTracks().forEach(track => track.stop()); // 停止所有媒体轨道
        }
        recorder.ondataavailable = null;
        recorder.onstop = null;
        mediaRecorderInstance.value = null;
    }
    // 重置状态变量
    isDictating.value = false;
    isAwaitingDictationChoice.value = false;
    audioChunks.value = [];
    recordedAudioBlob.value = null;
    dictationBaseText = "";
    audioUrlForNextMessage.value = null; // 确保清除待发送的音频 URL

    if (resolveMediaRecorderStoppedPromise) { // 清理 Promise resolve 函数
        resolveMediaRecorderStoppedPromise = null;
    }
    // 如果对话框可见，重新聚焦输入框
    if (dialogVisible.value) {
        nextTick(() => inputRef.value?.focus());
    }
};

/**
 * 切换听写状态（仅用于开始听写）。
 * 停止操作由确认/丢弃按钮处理。
 */
const toggleDictation = async () => {
    if (!canUseSpeechRecognition || !canUseMediaRecorder) {
        ElMessage.warning('您的浏览器不支持听写所需功能。');
        return;
    }
    if (isDictating.value) { // 此按钮理论上在 isDictating 为 true 时不可见，此为保险
        console.warn("toggleDictation 在 isDictating 为 true 时被调用，执行重置。");
        resetDictationStateCompletely();
        return;
    }
    await startDictationInternal(); // 调用内部开始听写函数
};

/**
 * 内部函数：开始听写过程（获取权限、初始化录音和语音识别）。
 */
const startDictationInternal = async () => {
    if (isAiResponding.value) {
        ElMessage.info('AI 正在响应，请稍后再开始听写。');
        return;
    }
    if (isAwaitingDictationChoice.value) { // 如果意外处于等待选择状态，先重置
        resetDictationStateCompletely();
        await nextTick(); // 等待DOM更新和状态清理
    }

    try {
        const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); // 获取麦克风权限
        isDictating.value = true; // 成功获取权限后，标记为正在听写，UI随之改变

        // 初始化听写状态
        audioChunks.value = [];
        recordedAudioBlob.value = null;
        isAwaitingDictationChoice.value = false;
        dictationBaseText = input.value; // 保存当前输入框文本作为基础

        // 初始化 MediaRecorder
        mediaRecorderInstance.value = new MediaRecorder(stream, { mimeType: 'audio/webm' }); // 使用 webm 格式
        mediaRecorderInstance.value.ondataavailable = (event) => { // 收集音频数据块
            if (event.data.size > 0) audioChunks.value.push(event.data);
        };
        mediaRecorderInstance.value.onstop = () => { // 录音停止时处理
            if (mediaRecorderInstance.value?.stream) { // 停止媒体流轨道
                mediaRecorderInstance.value.stream.getTracks().forEach(track => track.stop());
            }
            if (audioChunks.value.length > 0) { // 如果有录音数据
                recordedAudioBlob.value = new Blob(audioChunks.value, { type: 'audio/webm' }); // 合并为 Blob
                isAwaitingDictationChoice.value = true; // 标记音频已准备好，等待用户选择
            } else {
                recordedAudioBlob.value = null;
                isAwaitingDictationChoice.value = false;
            }
            // 如果有等待 MediaRecorder 停止的 Promise，则 resolve 它
            if (resolveMediaRecorderStoppedPromise) {
                resolveMediaRecorderStoppedPromise();
                resolveMediaRecorderStoppedPromise = null;
            }
        };
        mediaRecorderInstance.value.start(); // 开始录音

        // 初始化 SpeechRecognition
        recognitionInstance.value = new SpeechRecognitionAPI();
        recognitionInstance.value.lang = 'zh-CN'; // 设置语言为中文
        recognitionInstance.value.interimResults = true; // 返回临时识别结果
        recognitionInstance.value.continuous = true; // 持续识别

        recognitionInstance.value.onresult = (event: any) => { // 处理识别结果
            let interimTranscript = ''; // 临时结果字符串
            let finalSegment = '';    // 本次事件中的最终结果片段
            for (let i = event.resultIndex; i < event.results.length; ++i) {
                const transcript = event.results[i][0].transcript;
                if (event.results[i].isFinal) {
                    finalSegment += transcript;
                } else {
                    interimTranscript += transcript;
                }
            }
            input.value = dictationBaseText + finalSegment + interimTranscript; // 更新输入框内容
            if (finalSegment) { // 如果有最终片段，更新基础文本
                dictationBaseText += finalSegment;
            }
        };
        recognitionInstance.value.onerror = (event: any) => { // 处理识别错误
            console.error('语音识别错误:', event.error);
            ElMessage.error(`语音识别错误: ${event.error}`);
            resetDictationStateCompletely(); // 出错则重置听写状态
        };
        recognitionInstance.value.onend = () => { // 语音识别结束时
            // 如果是语音识别先结束（例如超时或静音），而录音仍在进行，则停止录音
            if (isDictating.value && mediaRecorderInstance.value && mediaRecorderInstance.value.state === "recording") {
                mediaRecorderInstance.value.stop(); // 这会触发 MediaRecorder 的 onstop
            }
            // isDictating 状态由用户的选择按钮（丢弃/确认）来重置
        };
        recognitionInstance.value.start(); // 开始语音识别
        ElMessage.success('听写已开始...');
    } catch (err: any) {
        console.error('启动听写失败:', err);
        ElMessage.error(`启动听写失败: ${err.message || '请检查麦克风权限和设置。'}`);
        resetDictationStateCompletely(); // 启动失败则重置
    }
};

/**
 * （未使用）早期设想的函数，用于主动停止听写并等待选择。
 * 当前逻辑下，停止操作由用户点击“确认”或“丢弃”按钮隐式触发，或在识别/录音出错时触发。
 */
// const stopDictationAndAwaitChoice = () => {
//     if (recognitionInstance.value) {
//         try { recognitionInstance.value.stop(); } catch (e) { console.warn("停止语音识别出错:", e); }
//     }
//     else if (mediaRecorderInstance.value && mediaRecorderInstance.value.state === "recording") {
//         try { mediaRecorderInstance.value.stop(); } catch (e) { console.warn("直接停止录音出错:", e); }
//     }
//     // isDictating.value = false; // 显式设置，以防 SR.onend 未触发
// };

/**
 * 处理用户点击“丢弃录音”按钮的操作。
 */
const handleDiscardDictation = () => {
    if (recognitionInstance.value) { // 停止语音识别
        try { recognitionInstance.value.stop(); } catch (e) { console.warn("丢弃时停止语音识别出错:", e); }
    }
    if (mediaRecorderInstance.value && mediaRecorderInstance.value.state === "recording") { // 停止录音
        try { mediaRecorderInstance.value.stop(); } catch (e) { console.warn("丢弃时停止录音出错:", e); }
    }
    ElMessage.info('录音已丢弃。');
    // input.value 中可能包含部分听写文本，用户可手动清除或编辑
    audioUrlForNextMessage.value = null; // 清除可能已暂存的音频 URL
    resetDictationStateCompletely(); // 完全重置听写状态
};

/**
 * 处理用户点击“确认并使用录音”按钮的操作。
 * 停止录音和识别 -> 上传音频 -> 发送消息（包含文本和音频URL）。
 */
const handleUploadAndApplyDictation = async () => {
    const mrWasRecording = mediaRecorderInstance.value && mediaRecorderInstance.value.state === "recording";

    if (recognitionInstance.value) { // 停止语音识别
        try { recognitionInstance.value.stop(); } catch (e) { console.warn("上传前停止语音识别出错:", e); }
    }

    if (mrWasRecording) { // 如果录音仍在进行
        const mediaRecorderStopPromise = new Promise<void>((resolve) => { // 创建 Promise 等待录音停止
            resolveMediaRecorderStoppedPromise = resolve;
        });
        try {
            mediaRecorderInstance.value!.stop(); // 停止录音
            await mediaRecorderStopPromise; // 等待 onstop 回调完成
        } catch (e) {
            console.warn("上传前停止录音或等待其停止时出错:", e);
            // 即使停止出错，如果已有音频数据块，尝试创建 Blob
            if (audioChunks.value.length > 0 && !recordedAudioBlob.value) {
                recordedAudioBlob.value = new Blob(audioChunks.value, { type: 'audio/webm' });
                isAwaitingDictationChoice.value = true; // 确保状态正确
            }
        }
    } else if (audioChunks.value.length > 0 && !recordedAudioBlob.value) {
        // 如果录音已停止但 Blob 未创建（例如 SR 先停止），则创建 Blob
        recordedAudioBlob.value = new Blob(audioChunks.value, { type: 'audio/webm' });
        isAwaitingDictationChoice.value = true;
    }

    if (!recordedAudioBlob.value) { // 如果没有录音数据
        ElMessage.warning('没有录音数据可上传。');
        resetDictationStateCompletely();
        return;
    }

    // 创建 File 对象准备上传
    const audioFile = new File([recordedAudioBlob.value], `dictation-audio-${props.user?.ID || 'anon'}-${Date.now()}.webm`, { type: 'audio/webm' });
    try {
        ElMessage.info({ message: '正在上传录音...', duration: 1500 });
        // 1. 上传音频到

    } catch (uploadError) {
        console.error('录音上传失败:', uploadError);
        ElMessage.error('录音上传失败，请稍后重试。');
        audioUrlForNextMessage.value = null; // 上传失败则清除 URL
    } finally {
        resetDictationStateCompletely(); // 无论成功失败，最终都重置听写状态
    }
};

// --- 浮动按钮 GIF 逻辑 ---
const currentFloatingButtonGif = computed(() => { // 根据 AI 状态计算应显示的 GIF
    if (shouldShowThinkingIndicator.value) return ThinkingGif; // AI 思考中
    if (isStreaming.value) return TalkingGif;                // AI 正在讲话（流式输出）
    if (isOnError.value) return SadGif;                     // 发生错误
    return DefaultGif;                                      // 默认状态
});

// --- 消息显示逻辑 ---
const displayMessages = computed(() => { // 计算用于在界面上显示的消息列表
    const msgs = messages.value;
    // 如果 AI 正在响应且最后一条消息是 AI 的空消息（流式输出刚开始），则暂时不显示这条空消息
    if (isAiResponding.value && msgs.length > 0) {
        const lastMessage = msgs[msgs.length - 1];
        if (lastMessage.role === MessageRole.Assistant && !lastMessage.content?.trim()) {
            return msgs.slice(0, -1);
        }
    }
    return msgs;
});

const shouldShowThinkingIndicator = computed(() => { // 是否应显示“AI正在思考中...”的指示器
    if (!isAiResponding.value) return false; // AI 未响应则不显示
    const originalMsgs = messages.value;
    if (originalMsgs.length === 0) return true; // 消息列表为空且 AI 在响应，则显示
    const lastOriginalMessage = originalMsgs[originalMsgs.length - 1];
    if (lastOriginalMessage.role === MessageRole.User) return true; // 最后一条是用户消息且 AI 在响应，则显示
    // 最后一条是 AI 消息但内容为空（流式输出刚开始）且 AI 在响应，则显示
    if (lastOriginalMessage.role === MessageRole.Assistant && !lastOriginalMessage.content?.trim()) {
        return true;
    }
    return false;
});

/**
 * 解析消息内容，分离文本和音频播放器标记。
 * @param {string} content 消息的原始字符串内容
 * @returns {{ text: string; audioUrl: string | null }} 解析后的文本和音频 URL
 */
function parseMessageContentForAudio(content: string): { text: string; audioUrl: string | null } {
    if (!content) return { text: '', audioUrl: null };
    const audioPattern = / \[AUDIO_PLAYER:(https?:\/\/[^\]]+)\]$/; // 匹配末尾的音频播放器标记
    const match = content.match(audioPattern);

    if (match && match[1]) { // 如果匹配成功
        const audioUrl = match[1]; // 提取 URL
        const textContent = content.substring(0, match.index).trim(); // 提取标记前的文本
        return { text: textContent, audioUrl: audioUrl };
    }
    return { text: content, audioUrl: null }; // 未匹配则返回原始内容和 null URL
}


/* --- 输入历史记录逻辑 --- */
const userMessages = computed(() => messages.value.filter(msg => msg.role === MessageRole.User)); // 用户已发送的消息历史
const userMessageHistoryPointer = ref(0); // 历史记录指针，0 表示当前输入，1 表示最新历史，以此类推
const draftMessage = ref(""); // 浏览历史前保存的当前输入框草稿

/**
 * 处理向上箭头按键事件，用于浏览上一条历史消息。
 */
const handleArrowUp = () => {
    if (userMessages.value.length === 0) return; // 没有历史则不处理
    if (userMessageHistoryPointer.value === 0) { // 首次按上箭头，保存当前输入为草稿
        draftMessage.value = input.value;
    }
    if (userMessageHistoryPointer.value < userMessages.value.length) {
        userMessageHistoryPointer.value++; // 指针增加
        const historyIndex = userMessages.value.length - userMessageHistoryPointer.value;
        input.value = userMessages.value[historyIndex].content; // 从历史记录恢复内容
        // 将光标移到末尾
        nextTick(() => {
            const textarea = inputRef.value?.textarea;
            if (textarea) {
                textarea.setSelectionRange(input.value.length, input.value.length);
                textarea.focus();
            }
        });
    }
};

/**
 * 处理向下箭头按键事件，用于浏览下一条历史消息或恢复草稿。
 */
const handleArrowDown = () => {
    if (userMessages.value.length === 0) return; // 没有历史则不处理
    if (userMessageHistoryPointer.value > 1) {
        userMessageHistoryPointer.value--; // 指针减少
        const historyIndex = userMessages.value.length - userMessageHistoryPointer.value;
        input.value = userMessages.value[historyIndex].content; // 从历史记录恢复内容
    } else if (userMessageHistoryPointer.value === 1) { // 已到最旧历史，再按向下则恢复草稿
        userMessageHistoryPointer.value = 0;
        input.value = draftMessage.value;
    }
    // 将光标移到末尾
    nextTick(() => {
        const textarea = inputRef.value?.textarea;
        if (textarea) {
            textarea.setSelectionRange(input.value.length, input.value.length);
            textarea.focus();
        }
    });
};

/**
 * 重置输入历史浏览状态（通常在发送消息后调用）。
 */
const resetHistoryBrowse = () => {
    userMessageHistoryPointer.value = 0;
    draftMessage.value = ""; // 清空草稿
};

// --- 核心聊天操作 ---
/**
 * 打开 AI 聊天对话框.
 */
const openDialog = () => {
    if (didDrag.value) { // 如果刚完成拖拽操作（超过阈值），则不打开对话框
        return;
    }
    dialogVisible.value = true;
    if (!chatSessionId.value) manageChatSessionId(); // 初始化或加载会话 ID
    if (!currentUserMessageUuid.value) manageCurrentUserMessageUuid(); // 初始化或加载用户消息 UUID
    nextTick(() => {
        inputRef.value?.focus(); // 聚焦输入框
        scrollToBottom();       // 滚动到底部
    });
};

/**
 * 处理发送消息的逻辑（包括文本和可能的音频）。
 */
const handleSendMessage = async () => {
    // 当 handleUploadAndApplyDictation 调用此函数时，是预期的发送行为，
    // isDictating 的检查已移除，因为UI层面已通过 :disabled 和 v-if 控制。
    if (!input.value.trim() || isAiResponding.value) {
        return; // 输入为空或 AI 正在响应则不发送
    }

    const messageContent = input.value.trim(); // 获取并清理输入内容
    recordAiLog(messageContent, MessageRole.User, currentUserMessageUuid.value); // 记录用户发送日志
    resetHistoryBrowse(); // 重置输入历史浏览状态

    const requestBody: any = { // 构建请求体
        userid: props.user?.ID,
        user: `用户ID:${props.user?.ID},用户名:${props.user?.name},当前团队ID:${props.user?.teamid}`,
        data: props.contextDataString,
        uuid: chatSessionId.value,
        tenant_id: tenant_id,
    };

    if (audioUrlForNextMessage.value) { // 如果有待发送的音频 URL
        requestBody.audio = audioUrlForNextMessage.value; // 添加到请求体
    }

    await handleSubmit(null, { // 调用 useChat 的 handleSubmit 发送请求
        body: requestBody
    });

    manageCurrentUserMessageUuid(true); // 发送后重置当前用户消息 UUID
    scrollToBottom(); // 滚动到底部
    audioUrlForNextMessage.value = null; // 清除已发送的音频 URL
};

/**
 * 停止 AI 的回复生成。
 */
const stopChat = () => {
    stop(); // 调用 useChat 的 stop 函数
    if (isDictating.value) { // 如果正在听写中途停止，也重置听写状态
        resetDictationStateCompletely();
    }
    ElMessage.info('AI 生成已停止');
};

/**
 * 清空聊天记录并开启新的会话。
 */
const clearChatHistory = () => {
    stop(); // 停止可能正在进行的 AI 回复
    if (isDictating.value) { // 如果正在听写，也重置
        resetDictationStateCompletely();
    }
    setMessages([]); // 清空消息列表
    resetHistoryBrowse(); // 重置输入历史
    manageChatSessionId(true); // 生成新的会话 ID
    manageCurrentUserMessageUuid(true); // 生成新的用户消息 ID
    ElMessage.success('聊天记录已清空');
    nextTick(() => inputRef.value?.focus()); // 重新聚焦输入框
};

// --- 工具函数 ---
/**
 * 滚动消息容器到底部。
 */
const scrollToBottom = () => {
    nextTick(() => { // 确保在 DOM 更新后执行
        const container = messagesContainerRef.value;
        if (container) {
            container.scrollTop = container.scrollHeight;
        }
    });
};

/**
 * 处理消息容器滚动事件，以控制“滚动到底部”按钮的显隐。
 */
const handleMessagesContainerScroll = () => {
    const container = messagesContainerRef.value;
    if (container) {
        const { scrollTop, scrollHeight, clientHeight } = container;
        // 如果滚动条不在底部（向上滚动超过阈值），则显示按钮
        showScrollToBottomButton.value = (scrollHeight - scrollTop - clientHeight > SCROLL_THRESHOLD);
    }
};

/**
 * 点击“滚动到底部”按钮的处理函数。
 */
const handleScrollToBottomClick = () => {
    scrollToBottom();
};

/**
 * 格式化 AI 的响应内容，主要是将 Markdown 转换为 HTML。
 * @param {string} content AI 响应的原始 Markdown 字符串
 * @returns {string} 格式化后的 HTML 字符串
 */
const formatAIResponse = (content: string) => {
    // 简单的 XSS 清理：移除 script 标签和 on* 事件处理器
    const cleanContent = content
        .replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '')
        .replace(/on\w+="[^"]*"/g, '');
    marked.setOptions({ // 配置 marked 解析器
        breaks: true, // 启用 GFM 换行符 (回车转<br>)
        gfm: true,    // 启用 GFM 表格等扩展语法
    });
    return marked.parse(cleanContent || ''); // 解析 Markdown
};

// --- 日志记录 ---
/**
 * 记录 AI 聊天相关的日志。
 * @param {string} content 消息内容
 * @param {MessageRole} role 角色（用户或助手）
 * @param {string} messageId 消息的 UUID
 * @param {string} [action] 可选的动作标识符 (例如 "UPLOAD_AUDIO_DICTATION")
 */
function recordAiLog(content: string, role: MessageRole, messageId: string, action?: AILogAction | string): void {
    const logEntry: [string, any][] = [];
    logEntry.push(["_method_", AILogMethod.AIChat]); // 日志方法类型
    logEntry.push(["type", "AI"]);                   // 日志主类型

    const body: { [key: string]: any } = { // 日志主体内容
        role: role,
        content: content.substring(0, MAX_LOG_CONTENT_LENGTH), // 截断过长内容
        message_uuid: messageId,
        chat_uuid: chatSessionId.value,
        action: action || (role === MessageRole.User ? AILogAction.SEND_MESSAGE : AILogAction.RECEIVE_RESPONSE),
        tenant_id: tenant_id,
    };
    logEntry.push(['body', JSON.stringify(body)]);
    console.log("记录AI日志:", logEntry); // 调试输出
    // 实际项目中应调用 jilurizhi(logEntry);
    return; // 移除 return jilurizhi(...) 以便 console.log 生效
    // jilurizhi(logEntry);
}

// --- Watchers 与生命周期钩子 ---
watch(messages, () => { // 监听消息数组变化
    // 只有当滚动条接近底部时才自动滚动，避免用户向上查看历史消息时被强制拉回
    const container = messagesContainerRef.value;
    if (container) {
        const { scrollTop, scrollHeight, clientHeight } = container;
        const isAtBottom = scrollHeight - scrollTop - clientHeight < SCROLL_THRESHOLD + 50; // 增加一些容差
        if (isAtBottom || messages.value.length <= 1) { // 如果在底部或只有一条消息
            scrollToBottom();
        }
    } else { // 容器未就绪时也尝试滚动（例如初始加载）
        scrollToBottom();
    }
}, { deep: true }); // 深度监听

watch(dialogVisible, (isVisible) => { // 监听对话框可见性变化
    if (isVisible) { // 对话框打开时
        nextTick(() => {
            if (messagesContainerRef.value) {
                messagesContainerRef.value.addEventListener('scroll', handleMessagesContainerScroll);
                handleMessagesContainerScroll(); // 初始检查一次滚动状态
            }
            // 监听 iframe 消息
            if (voiceIframeRef.value && chatMode.value === 'voice') {
                setupIframeMessageListener();
            }
        });
    } else { // 对话框关闭时
        if (messagesContainerRef.value) {
            messagesContainerRef.value.removeEventListener('scroll', handleMessagesContainerScroll);
        }
        showScrollToBottomButton.value = false; // 隐藏滚动按钮
        if (contextMenuVisible.value) { // 如果对话框关闭时自定义菜单还开着，也关掉
            hideContextMenu();
        }
        clearIframeMessageListener();
    }
});

let heightCheckInterval: NodeJS.Timeout | null = null;
let lastRequestTime = 0; // 记录最后一次请求时间
let isHeightAdjusted = false; // 记录是否已经调整过高度

/**
 * 处理来自 iframe 的消息（高度和关闭信号）
 */
const handleIframeMessage = (event: MessageEvent) => {
    const iframe = voiceIframeRef.value;
    if (!iframe) {
        console.log('iframe 引用不存在');
        return;
    }

    console.log('收到 postMessage:', event.origin, event.data);

    // 先检查消息数据类型
    if (!event.data || typeof event.data !== 'object') {
        console.log('消息数据格式错误:', event.data);
        return;
    }

    // 验证消息来源（放宽验证条件）
    try {
        const iframeUrl = new URL(computedIframeUrl.value);
        console.log('iframe URL origin:', iframeUrl.origin, 'event origin:', event.origin);

        // 如果是开发环境，可能存在端口不匹配的情况
        if (process.env.NODE_ENV === 'development') {
            // 开发环境下放宽限制，只检查主机名
            const iframeHostname = iframeUrl.hostname;
            const eventHostname = new URL(event.origin).hostname;
            if (iframeHostname !== eventHostname && eventHostname !== 'localhost' && eventHostname !== '127.0.0.1') {
                console.warn('开发环境下主机名不匹配:', iframeHostname, eventHostname);
                return;
            }
        } else {
            // 生产环境下严格检查
            if (event.origin !== iframeUrl.origin) {
                console.warn('生产环境下域名不匹配:', iframeUrl.origin, event.origin);
                return;
            }
        }
    } catch (e) {
        console.error('验证消息来源时出错:', e);
        // 如果 URL 解析失败，继续处理消息（可能是开发环境）
    }

    // 处理不同类型的消息
    if (event.data.type === IframeMessageType.Height && typeof event.data.height === 'number') {
        // 处理高度变化消息
        console.log('处理高度消息:', event.data.height);

        const newHeight = event.data.height;
        const minHeight = 400;
        const maxHeight = window.innerHeight * 0.8;
        const finalHeight = Math.min(Math.max(newHeight, minHeight), maxHeight);

        console.log('计算出的最终高度:', finalHeight, '当前iframe高度:', iframe.style.height);

        if (iframe.style.height !== finalHeight + 'px') {
            iframe.style.height = finalHeight + 'px';
            console.log(`iframe 高度调整为: ${finalHeight}px`);
        }
    } else if (event.data.type === IframeMessageType.Close) {
        // 处理 iframe 关闭信号，直接关闭对话框
        console.log('收到 iframe 关闭信号，关闭对话框');
        dialogVisible.value = false;
    } else {
        console.log('未知消息类型:', event.data.type);
    }
};

/**
 * 设置 iframe 消息监听
 */
const setupIframeMessageListener = () => {
    const iframe = voiceIframeRef.value;
    if (!iframe) return;

    console.log('设置 iframe 消息监听，URL:', computedIframeUrl.value);

    // 监听来自 iframe 的消息
    window.addEventListener('message', handleIframeMessage);

    // iframe 加载完成后设置默认高度
    iframe.onload = () => {
        console.log('iframe 加载完成');

        // 设置默认高度
        if (!iframe.style.height || iframe.style.height === '0px') {
            iframe.style.height = '600px';
            console.log('设置 iframe 默认高度: 600px');
        }
    };

    // 添加错误处理
    iframe.onerror = (error) => {
        console.error('iframe 加载错误:', error);
    };
};

/**
 * 清理 iframe 消息监听
 */
const clearIframeMessageListener = () => {
    // 移除消息监听
    window.removeEventListener('message', handleIframeMessage);
};

onUnmounted(() => { // 组件卸载时
    // 清理滚动事件监听器
    if (messagesContainerRef.value && dialogVisible.value) {
        messagesContainerRef.value.removeEventListener('scroll', handleMessagesContainerScroll);
    }
    // 清理拖拽事件监听器
    if (isDragging.value) {
        document.removeEventListener('mousemove', handleMouseMove);
        document.removeEventListener('mouseup', handleMouseUp);
        document.removeEventListener('mouseleave', handleMouseUp);
    }
    stop(); // 停止任何正在进行的 AI 生成
    if (isDictating.value) { // 如果正在听写，则重置状态
        resetDictationStateCompletely();
    }
    document.removeEventListener('mousedown', handleClickOutside);
    clearIframeMessageListener(); // 清理 iframe 消息监听
});


</script>

<style scoped>

/* 浮动按钮 */
.ai-chat-floating-btn {
    position: fixed;
    /* 默认位置，拖拽后会被内联样式覆盖 */
    right: 0px;
    bottom: calc(50% - 75px);
    /* 根据GIF高度150px调整垂直居中 */
    z-index: 2010;
    padding: 0;
    display: flex;
    align-items: center;
    justify-content: center;
}

.floating-btn-gif {
    /* width: 28px; */
    filter: drop-shadow(0px 4px 6px rgba(0, 0, 0, .5));
    /* 应用轮廓阴影 */
    height: 150px;
    display: block;
}

.ai-chat-floating-btn .el-icon {
    font-size: 24px;
}

/* 自定义右键菜单样式 */
.custom-context-menu {
    position: absolute;
    /* 改为 absolute，相对于 .ai-chat-floating-btn 定位 */
    background-color: white;
    border: 1px solid #e0e0e0;
    /* 浅色边框 */
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
    /* 柔和阴影 */
    z-index: 2011;
    /* 比悬浮按钮高一级 */
    border-radius: 6px;
    /* 圆角 */
    padding: 6px 0;
    min-width: 130px;
    /* 略微增加宽度 */
    font-size: 14px;
}

.context-menu-item {
    padding: 8px 16px;
    /* 调整内边距 */
    cursor: pointer;
    color: #333;
    white-space: nowrap;

    display: flex;
    align-items: center;
    justify-content: center;
}

.context-menu-item:hover {
    background-color: #f5f5f5;
    /* 悬停背景色 */
}


/* 对话框通用样式 */
.el-dialog__header-title {
    display: flex;
    align-items: center;
    color: var(--el-text-color-primary);
    font-weight: 600;
}

.el-dialog__header-title .el-icon {
    margin-right: 8px;
    font-size: 1.2em;
}

.dialog-main-content {
    display: flex;
    flex-direction: column;
    max-height: 70vh;
    /* Default max height, primarily for text mode */
}

/* 对话框头部区域 */
.ai-chat-dialog-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 5px;
    flex-shrink: 0;
}

.dialog-clear-btn {
    display: flex;
    justify-content: flex-end;
    width: 100%;
    flex-shrink: 0;
}

.dialog-clear-btn .el-button {
    font-size: 13px;
    background-color: transparent;
    border-color: transparent;
    color: var(--el-text-color-regular);
    padding: 6px 10px;
}

.dialog-clear-btn .el-button:hover,
.dialog-clear-btn .el-button:focus {
    color: var(--el-color-primary);
    background-color: var(--el-color-primary-light-9);
    border-color: transparent;
}

.dialog-clear-btn .el-button:active {
    color: var(--el-color-primary-dark-2);
    background-color: var(--el-color-primary-light-8);
    border-color: transparent;
}

.dialog-clear-btn .el-button.is-disabled,
.dialog-clear-btn .el-button.is-disabled:hover,
.dialog-clear-btn .el-button.is-disabled:focus {
    color: var(--el-text-color-placeholder);
    background-color: transparent;
    border-color: transparent;
}

/* 消息容器 */
.chat-messages-container {
    overflow-y: auto;
    padding: 10px 15px;
    border: none;
    border-radius: 4px;
    background-color: #fff;
    flex-grow: 1;
    min-height: 0;
}

.empty-chat-placeholder {
    text-align: center;
    color: var(--el-text-color-secondary, #a8abb2);
    padding: 40px 20px;
    font-style: normal;
    font-size: 14px;
}

/* 消息行通用样式 */
.message-row {
    display: flex;
    margin-bottom: 12px;
    align-items: flex-start;
}

.message-avatar {
    width: 30px;
    height: 30px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 16px;
    margin-right: 10px;
    flex-shrink: 0;
    background-color: var(--el-bg-color-page, #f5f7fa);
    color: var(--el-text-color-primary);
}

.message-content-wrapper {
    display: flex;
    flex-direction: column;
    max-width: calc(100% - 48px);
    flex: 1;
}

.message-sender-name {
    font-size: 0.8em;
    color: var(--el-text-color-secondary, #909399);
    margin-bottom: 3px;
}

.message-bubble {
    padding: 10px 14px;
    border-radius: 16px;
    word-wrap: break-word;
    line-height: 1.65;
    font-size: 14px;
    box-shadow: none;
    border: 1px solid transparent;
}

/* 用户消息特定样式 */
.message-user .message-avatar {
    background-color: var(--el-color-primary-light-8);
    color: var(--el-color-primary);
}

.message-user .message-bubble {
    background-color: var(--el-color-primary);
    color: #fff;
    border-top-right-radius: 5px;
    white-space: pre-wrap;
    /* 保留用户输入的换行和空格 */
}

.message-user {
    /* 此规则影响布局，应与其他 .message-user 特定样式一起 */
    flex-direction: row-reverse;
}

.message-user .message-avatar {
    /* 此规则影响布局，应与其他 .message-user 特定样式一起 */
    margin-left: 10px;
    margin-right: 0;
}

.message-user .message-content-wrapper {
    /* 此规则影响布局，应与其他 .message-user 特定样式一起 */
    align-items: flex-end;
}

/* AI助手消息特定样式 */
.message-assistant .message-avatar {
    background-color: #e9e9eb;
    color: #3d3d3d;
}

.message-assistant .message-bubble {
    background-color: #f0f2f5;
    color: var(--el-text-color-primary, #303133);
    border-top-left-radius: 5px;
}

/* AI助手气泡内 Markdown 内容样式 */
.message-assistant .message-bubble :deep(code):not(pre > code) {
    color: #c7254e;
    background-color: #e6e8ea;
    padding: 0.1em 0.3em;
    border-radius: 3px;
}

.message-assistant .message-bubble :deep(pre) {
    margin: 8px 0;
    padding: 10px;
    border-radius: 6px;
    background-color: #e6e8ea;
}

.message-assistant .message-bubble :deep(pre code) {
    color: inherit;
    background-color: transparent;
    padding: 0;
}

.message-assistant .message-bubble :deep(p:first-child) {
    margin-top: 0;
}

.message-assistant .message-bubble :deep(p:last-child) {
    margin-bottom: 0;
}

.message-assistant .message-bubble :deep(:is(h1, h2, h3, h4, h5, h6, .h1, .h2, .h3, .h4, .h5, .h6)) {
    all: unset;
    display: block;
    box-sizing: border-box;
    font-weight: bold !important;
    margin-top: 12px !important;
    margin-bottom: 6px !important;
    line-height: 1.3 !important;
    font-size: 1em !important;
    border-bottom: none !important;
    padding-bottom: 0 !important;
    color: inherit;
}

.message-assistant .message-bubble :deep(p) {
    margin: 5px 0 !important;
}

.message-assistant .message-bubble :deep(ul),
.message-assistant .message-bubble :deep(ol) {
    margin: 5px 0 5px 20px !important;
    padding-left: 20px !important;
}

.message-assistant .message-bubble :deep(li) {
    margin-bottom: 1px !important;
}

.message-assistant .message-bubble :deep(blockquote) {
    border-left: 4px solid var(--el-color-primary-light-5) !important;
    background: var(--el-fill-color-lighter) !important;
    padding: 6px 10px !important;
    margin: 6px 0 !important;
    color: var(--el-text-color-secondary) !important;
    border-radius: 4px !important;
    font-size: 0.95em !important;
}

.message-assistant .message-bubble :deep(table) {
    border-collapse: collapse !important;
    margin: 8px 0 !important;
    width: 100% !important;
    font-size: 0.9em !important;
    box-shadow: 0 0 0 1px var(--el-border-color-lighter);
}

.message-assistant .message-bubble :deep(th),
.message-assistant .message-bubble :deep(td) {
    border: 1px solid var(--el-border-color-lighter) !important;
    padding: 6px 8px !important;
    text-align: left;
}

.message-assistant .message-bubble :deep(th) {
    background: var(--el-fill-color-light) !important;
    font-weight: 600 !important;
}

.message-assistant .message-bubble :deep(img) {
    max-width: 100% !important;
    height: auto !important;
    border-radius: 4px !important;
    margin: 5px 0 !important;
    display: block !important;
    border: 1px solid var(--el-border-color-extralight);
}

/* 聊天输入区域 */
.chat-input-area {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: stretch;
    margin-top: 10px;
    flex-shrink: 0;
    background: #f0f2f5;
    border-radius: 12px;
    border: 1px solid #e0e0e0;
    padding: 8px 10px;
    transition: border-color 0.2s, background-color 0.2s;
}

.chat-input-area:focus-within {
    border-color: var(--el-color-primary-light-3);
    background: #fff;
}

.chat-input-inner {
    flex-grow: 1;
    position: relative;
    display: flex;
    background: transparent;
}

.chat-textarea {
    width: 100%;
}

.chat-textarea :deep(.el-textarea__inner) {
    padding: 5px 8px !important;
    border-radius: 0 !important;
    line-height: 1.6;
    font-size: 14px;
    background: transparent !important;
    border: none !important;
    box-shadow: none !important;
    resize: none;
    color: var(--el-text-color-primary);
}

.chat-textarea :deep(.el-textarea__inner::placeholder) {
    color: var(--el-text-color-placeholder);
}

.chat-controls-row {
    display: flex;
    justify-content: flex-end;
    align-items: center;
    margin-top: 8px;
    gap: 8px;
}

.dictation-choices {
    display: flex;
    gap: 8px;
}


.send-stop-button {
    width: 32px;
    height: 32px;
    min-width: 32px;
    min-height: 32px;
    border-radius: 6px;
    background: var(--el-color-primary);
    color: #fff;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    border: none;
    box-shadow: none;
    transition: background-color 0.2s, transform 0.1s;
}

.chat-audio-player {
    display: block;
    margin-top: 8px;
    max-width: 100%;
    height: 40px;
    border-radius: 5px;
}

.message-text-content {
    white-space: pre-wrap;
    /* 保留文本部分的换行 */
}

.mode-switch-footer {
    padding: 8px 0px;
    margin-top: 4px;
    display: flex;
    border-top: 1px solid var(--el-border-color-lighter);
}

.mode-switch-footer.text-mode-footer {
    justify-content: flex-end;
    padding-right: 10px;
}

.mode-switch-footer.voice-mode-footer {
    justify-content: flex-end;
    padding-left: 10px;
    border-top: none !important;
    /* 移除语音模式下切换条的顶部边框 */
}

.dialog-main-content.voice-mode-main-content {
    display: flex;
    flex-direction: column;
    padding: 0;
    max-height: none;
    height: auto;
    /* 改为 auto，让内容决定高度 */
    min-height: 400px;
}

.voice-chat-header-text {
    text-align: center;
    padding: 12px 0 8px 0;
    font-size: 15px;
    color: var(--el-text-color-secondary);
    flex-shrink: 0;
    border-bottom: 1px solid var(--el-border-color-lighter);
    margin-bottom: 5px;
}

.voice-chat-iframe-element {
    width: 100%;
    border: none;
    transition: height 0.3s ease;
}
</style>

<style>
.el-dialog.voice-mode-dialog {
    margin-right: 0 !important;
    width: 50% !important;
}

.el-dialog.voice-mode-dialog {
    background: transparent !important;
    box-shadow: none !important;
    border: none !important;
    /* 确保对话框本身在语音模式下无边框 */
}

.el-dialog.voice-mode-dialog .el-dialog__header {
    border: none !important;
    box-shadow: var(--el-dialog-box-shadow);
    width: 50% !important;
    margin-left: auto !important;
}

.el-dialog.voice-mode-dialog .el-dialog__header .el-dialog__headerbtn {
    height: auto;
}

.el-dialog.voice-mode-dialog .el-dialog__body {
    background: transparent !important;
    padding: 0 !important;
}
</style>